/* global KEEP */

KEEP.initLocalSearch = () => {
  // Search DB path
  let searchPath = KEEP.hexo_config.path
  if (!searchPath) {
    // Search DB path
    console.warn('`hexo-generator-searchdb` plugin is not installed!')
    return
  }

  // Popup Window
  let isfetched = false
  let datas
  let isXml = true
  if (searchPath.length === 0) {
    searchPath = 'search.xml'
  } else if (searchPath.endsWith('json')) {
    isXml = false
  }
  const searchInputDom = document.querySelector('.search-input')
  const resultContent = document.getElementById('search-result')

  const getIndexByWord = (word, text, caseSensitive) => {
    let wordLen = word.length
    if (wordLen === 0) return []
    let startPosition = 0
    let position = []
    let index = []
    if (!caseSensitive) {
      text = text.toLowerCase()
      word = word.toLowerCase()
    }
    while ((position = text.indexOf(word, startPosition)) > -1) {
      index.push({ position, word })
      startPosition = position + wordLen
    }
    return index
  }

  // Merge hits into slices
  const mergeIntoSlice = (start, end, index, searchText) => {
    let item = index[index.length - 1]
    let { position, word } = item
    let hits = []
    let searchTextCountInSlice = 0
    while (position + word.length <= end && index.length !== 0) {
      if (word === searchText) {
        searchTextCountInSlice++
      }
      hits.push({
        position,
        length: word.length
      })
      let wordEnd = position + word.length

      // Move to next position of hit
      index.pop()
      while (index.length !== 0) {
        item = index[index.length - 1]
        position = item.position
        word = item.word
        if (wordEnd > position) {
          index.pop()
        } else {
          break
        }
      }
    }
    return {
      hits,
      start,
      end,
      searchTextCount: searchTextCountInSlice
    }
  }

  // Highlight title and content
  const highlightKeyword = (text, slice) => {
    let result = ''
    let prevEnd = slice.start
    slice.hits.forEach((hit) => {
      result += text.substring(prevEnd, hit.position)
      let end = hit.position + hit.length
      result += `<b class="search-keyword">${text.substring(hit.position, end)}</b>`
      prevEnd = end
    })
    result += text.substring(prevEnd, slice.end)
    return result
  }

  const inputEventFunction = () => {
    if (!isfetched) return
    let searchText = searchInputDom.value.trim().toLowerCase()
    let keywords = searchText.split(/[-\s]+/)
    if (keywords.length > 1) {
      keywords.push(searchText)
    }
    let resultItems = []
    if (searchText.length > 0) {
      // Perform local searching
      datas.forEach(({ title, content, url }) => {
        let titleInLowerCase = title.toLowerCase()
        let contentInLowerCase = content.toLowerCase()
        let indexOfTitle = []
        let indexOfContent = []
        let searchTextCount = 0
        keywords.forEach((keyword) => {
          indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false))
          indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false))
        })

        // Show search results
        if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
          let hitCount = indexOfTitle.length + indexOfContent.length
          // Sort index by position of keyword
          ;[indexOfTitle, indexOfContent].forEach((index) => {
            index.sort((itemLeft, itemRight) => {
              if (itemRight.position !== itemLeft.position) {
                return itemRight.position - itemLeft.position
              }
              return itemLeft.word.length - itemRight.word.length
            })
          })

          let slicesOfTitle = []
          if (indexOfTitle.length !== 0) {
            let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText)
            searchTextCount += tmp.searchTextCountInSlice
            slicesOfTitle.push(tmp)
          }

          let slicesOfContent = []
          while (indexOfContent.length !== 0) {
            let item = indexOfContent[indexOfContent.length - 1]
            let { position, word } = item
            // Cut out 100 characters
            let start = position - 20
            let end = position + 80
            if (start < 0) {
              start = 0
            }
            if (end < position + word.length) {
              end = position + word.length
            }
            if (end > content.length) {
              end = content.length
            }
            let tmp = mergeIntoSlice(start, end, indexOfContent, searchText)
            searchTextCount += tmp.searchTextCountInSlice
            slicesOfContent.push(tmp)
          }

          // Sort slices in content by search text's count and hits' count
          slicesOfContent.sort((sliceLeft, sliceRight) => {
            if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
              return sliceRight.searchTextCount - sliceLeft.searchTextCount
            } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
              return sliceRight.hits.length - sliceLeft.hits.length
            }
            return sliceLeft.start - sliceRight.start
          })

          // Select top N slices in content
          let upperBound = parseInt(
            KEEP.theme_config.local_search.top_n_per_article
              ? KEEP.theme_config.local_search.top_n_per_article
              : 1,
            10
          )
          if (upperBound >= 0) {
            slicesOfContent = slicesOfContent.slice(0, upperBound)
          }

          let resultItem = ''

          if (slicesOfTitle.length !== 0) {
            resultItem += `<li><a href="${url}" class="search-result-title">${highlightKeyword(
              title,
              slicesOfTitle[0]
            )}</a>`
          } else {
            resultItem += `<li><a href="${url}" class="search-result-title">${title}</a>`
          }

          slicesOfContent.forEach((slice) => {
            resultItem += `<a href="${url}"><p class="search-result">${highlightKeyword(
              content,
              slice
            )}...</p></a>`
          })

          resultItem += '</li>'
          resultItems.push({
            item: resultItem,
            id: resultItems.length,
            hitCount,
            searchTextCount
          })
        }
      })
    }
    if (keywords.length === 1 && keywords[0] === '') {
      resultContent.innerHTML = '<div id="no-result"><i class="fas fa-search fa-5x"></i></div>'
    } else if (resultItems.length === 0) {
      resultContent.innerHTML = '<div id="no-result"><i class="fas fa-box-open fa-5x"></i></div>'
    } else {
      resultItems.sort((resultLeft, resultRight) => {
        if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
          return resultRight.searchTextCount - resultLeft.searchTextCount
        } else if (resultLeft.hitCount !== resultRight.hitCount) {
          return resultRight.hitCount - resultLeft.hitCount
        }
        return resultRight.id - resultLeft.id
      })
      let searchResultList = '<ul class="search-result-list">'
      resultItems.forEach((result) => {
        searchResultList += result.item
      })
      searchResultList += '</ul>'
      resultContent.innerHTML = searchResultList
      window.pjax && window.pjax.refresh(resultContent)
    }
  }

  const fetchData = () => {
    fetch(KEEP.hexo_config.root + searchPath)
      .then((response) => response.text())
      .then((res) => {
        // Get the contents from search data
        isfetched = true
        datas = isXml
          ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(
              (element) => {
                return {
                  title: element.querySelector('title').textContent,
                  content: element.querySelector('content').textContent,
                  url: element.querySelector('url').textContent
                }
              }
            )
          : JSON.parse(res)
        // Only match articles with not empty titles
        datas = datas
          .filter((data) => data.title)
          .map((data) => {
            data.title = data.title.trim()
            data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''
            data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/')
            return data
          })
        // Remove loading animation
        const noResultDom = document.querySelector('#no-result')
        noResultDom && (noResultDom.innerHTML = '<i class="fas fa-search fa-5x"></i>')
      })
  }

  if (KEEP.theme_config.local_search.preload) {
    fetchData()
  }

  if (searchInputDom) {
    searchInputDom.addEventListener('input', inputEventFunction)
  }

  // Handle and trigger popup window
  document.querySelectorAll('.search-popup-trigger').forEach((element) => {
    element.addEventListener('click', () => {
      document.body.style.overflow = 'hidden'
      document.querySelector('.search-pop-overlay').classList.add('active')
      setTimeout(() => searchInputDom.focus(), 500)
      if (!isfetched) fetchData()
    })
  })

  // Monitor main search box
  const onPopupClose = () => {
    document.body.style.overflow = ''
    document.querySelector('.search-pop-overlay').classList.remove('active')
  }

  document.querySelector('.search-pop-overlay').addEventListener('click', (event) => {
    if (event.target === document.querySelector('.search-pop-overlay')) {
      onPopupClose()
    }
  })
  document.querySelector('.search-input-field-pre').addEventListener('click', () => {
    searchInputDom.value = ''
    searchInputDom.focus()
    inputEventFunction()
  })
  document.querySelector('.close-popup-btn').addEventListener('click', onPopupClose)
  window.addEventListener('pjax:success', onPopupClose)
  window.addEventListener('keyup', (event) => {
    if (event.key === 'Escape') {
      onPopupClose()
    }
  })
}
